# This code is hosted on http://code.google.com/p/lenthorp/
# Freely available for use in applications, but should NOT be modified
# Email all comments to lenthorpresearch@gmail.com


import PricingEngineMod
from PricingEngineMod import *
reload(PricingEngineMod)

from math import pow
import scipy.optimize
from scipy.optimize import fmin_bfgs, fmin_slsqp


class DistanceMetric():

    def getDistance(self, input1, input2, weights):
        pass


class SumOfSquaresDistance(DistanceMetric):

    def getDistance(self, input1, input2, weights):
        if not len(input1) == len(input2):
            raise Exception, 'inputs need to be the same length'
        currDist = 0.0
        for idx in range(len(input1)):
            currDist += pow(input1[idx] - input2[idx], 2) * weights[idx]
        return currDist


class Calibrator():

    def __init__(self, pricingEng, pricingParams, optimParams, distMetric):
        self.pricingEng = pricingEng
        self.optimParams = optimParams
        self.distMetric = distMetric
        self.pricingEng.setUpParameters(pricingParams, optimParams)

    def evalFunc(self, paras):
        #results = [0 for idx in range(pricingEng.numberOfPricingParams)]
        self.pricingEng.updateOptimiseParams(paras)
        self.pricingEng.preComputations()
        calcVals = [0 for idx in range(self.pricingEng.numberOfPricingParams)]
        targetVals = [0 for idx in range(self.pricingEng.numberOfPricingParams)]
        weights = [0 for idx in range(self.pricingEng.numberOfPricingParams)]
        for idx in range(self.pricingEng.numberOfPricingParams):
            self.pricingEng.setPricingParams(idx)
            calcVals[idx] = self.pricingEng.getPrice()
            targetVals[idx] = self.pricingEng.targetPrice
            weights[idx] = self.pricingEng.targetWeight
        dist = self.distMetric.getDistance(calcVals, targetVals, weights)
        return dist

    def calibrate(self, args = []):
        pass

    def calibResults(self, results):
        returnDict = {}
        for idx in range(len(results)):
            returnDict[self.pricingEng.storedOptimiseNames[idx]] = results[idx]
        return returnDict


class ScipyFminBFGSCalibrator(Calibrator):

    def calibrate(self, args = []):
        currentParams = [0 for idx in range(self.optimParams.getSize())]
        count = 0
        for name in self.pricingEng.storedOptimiseNames:
            currentParams[count] = self.optimParams.p[name]
            count += 1
        currentParams = fmin_bfgs(self.evalFunc, currentParams, disp=False)
        return self.calibResults(currentParams)


class ScipyFminSLSQPCalibrator(Calibrator):

    def calibrate(self, args = []):
        currentParams = [0 for idx in range(self.optimParams.getSize())]
        bounds = [() for idx in range(self.optimParams.getSize())]
        count = 0
        for name in self.pricingEng.storedOptimiseNames:
            currentParams[count] = self.optimParams.p[name]
            
            if self.optimParams.Bounds.has_key(name + '_min') and self.optimParams.Bounds.has_key(name + '_max'):
                bounds[count] = (self.optimParams.Bounds[name + '_min'], self.optimParams.Bounds[name + '_max'])
            else:
                if self.optimParams.Bounds.has_key(name + '_min'):
                    bounds[count] = (self.optimParams.Bounds[name + '_min'], None)
                if self.optimParams.Bounds.has_key(name + '_max'):
                    bounds[count] = (None, self.optimParams.Bounds[name + '_max'])
            count += 1
        currentParams = fmin_slsqp(self.evalFunc, currentParams, bounds=bounds, iter=30, epsilon = 0.01)
        return self.calibResults(currentParams)    
        